package com.example.sid.how_to_use

import android.app.Application
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.annotation.ColorInt
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import com.example.sid.utils.Utils
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import sberid.sdk.app_token.domain.models.models.App2SdkModel
import sberid.sdk.app_token.utils.events.AppTokenEvents
import sberid.sdk.auth.login.SberIDLoginManager.Companion.SberIDBuilder
import sberid.sdk.auth.model.SberIDResultModel
import sberid.sdk.auth.model.StandName
import sberid.sdk.global.models.CustomFonts
import sberid.sdk.global.models.LinkInformation
import sberid.sdk.global.models.OIDCParams
import sberid.sdk.init.SID
import sberid.sdk.ui.models.root.UIColorMode

/**
 * Настойчиво рекомендуем прятать библиотеку под фасад, для централизированного изменения, в случае смены API.
 * Также вы будете знать, про инициализированна у вас библиотека или нет и это поможет вам при работе с ней.
 *
 * !!! Это эксперементальный фасад !!!
 */
object AsyncSIDFacade {

	private val initalizeChanel = Channel<Boolean>()

	private suspend fun <T> runIfInitialized(body: suspend () -> T): T {
		if (!Utils.getInitialized()) {
			Log.e("AsyncSIDFacade", "Поток ожидает инициализации библиотеки")
			initalizeChanel.receive()
			Log.e("AsyncSIDFacade", "Поток продолжает работу")
		}
		return body.invoke()
	}

	/**
	 * 	Все функции ниже можно сделать синхронными, добавив лямбду в функцию, которая будет возвращать результат.
	 * 	Как пример -
	 *
	 * 	fun getIDAuthResult(intent: Intent?, result: (SberIDResultModel?) -> Unit) = syncRunIfInitialized(result) {
	 * 		if (intent == null) return@syncRunIfInitialized null
	 * 		val model = SIDFacade.getIDAuthResult(intent = intent)
	 * 		model
	 * 	}
	 */
	private fun <T> syncRunIfInitialized(result: (T) -> Unit, body: () -> T) {
		Utils.defaultScope.launch {
			if (!Utils.getInitialized()) {
				initalizeChanel.receive()
			}
			val bodyResult = body.invoke()
			result.invoke(bodyResult)
		}
	}



	//Initializer

	/**
	 * Метод инициализирует SDK.
	 *
	 * @param application необходим для создания зависимостей
	 * @param clientId нужен для проверки доступности SDK для партнера
	 * @param partnerName имя партнёра
	 * @param partnerProfileUrl бекенд профиля партнёра
	 * @param themeColorLight цвет светлой темы
	 * @param themeColorDark цвет тёмной темы
	 * @param isShowErrorOnMain показывать или нет ошибку в виде кота при работе ELK на главном экране
	 */
	@OptIn(ExperimentalCoroutinesApi::class)
	suspend fun initialize(
		application: Application,
		clientId: String,
		partnerName: String?,
		partnerProfileUrl: String?,
		@ColorInt themeColorLight: Int?,
		@ColorInt themeColorDark: Int?,
		isShowErrorOnMain: Boolean = false
	) {
		SID.Initializer.initialize(
			application = application,
			clientId = clientId,
			partnerName = partnerName,
			partnerProfileUrl = partnerProfileUrl,
			themeColorLight = themeColorLight,
			themeColorDark = themeColorDark,
			isShowErrorOnMain = isShowErrorOnMain
		)

		while (initalizeChanel.isEmpty){
			initalizeChanel.send(true)
		}
		initalizeChanel.close()
		Utils.setInitialized()
	}


	//Login

	/**
	 * Переключить стенд.
	 *
	 * @param stand стенд
	 */
	suspend fun createLoginManager(stand: StandName) = runIfInitialized {
		SID.Login.createLoginManager(
			stand = stand
		)
	}

	/**
	 * Создать URI для логина клиента партнёром.
	 *
	 * @param clientId Идентификатор, полученный при регистрации партнера в личном кабинете Сбер ID.
	 * @param scope Список передаваемых данных на которые подписана ваше приложение, указывать стоит через пробел, например "openid previous_name phones". Значение openid является обязательным и располагается на первой позиции.
	 * @param state Для предотвращения подделки общения между приложениями используется генерируемое случайным образом уникальное значение..
	 * @param nonce Это значение обычно представляет собой случайную уникальную строку или глобальный уникальный идентификатор, которые можно использовать для определения источника запроса. Ограничение по длине значения составляет 64 символа.
	 * @param redirectUri Адрес, куда будет перенаправлен клиент после окончания аутентификации. Возврат происходит и при сценарии с ошибкой.
	 * @param customTabRedirectUri Адрес активити, куда будет перенаправлен клиент после окончания идентификации для продолжения авторизаци в том же самом CustomTab, в котором запустился процесс входа. Возврат происходит и при сценарии с ошибкой. Не нужен для обычного входа
	 * @param codeChallenge Хэшированное значение секретного кода code_verifier партнера. Хэширование выполняется методом, указанным в code_challenge_method. code_challenge = BASE64URL- ENCODE (SHA256 (ASCII (code_verifier)))).
	 * @param codeChallengeMethod Метод преобразования, который был применен к коду проверки (code_verifier), для получения значения кода вызова (code_challenge).
	 * @param uriScheme Кастомная схема диплинка авторизации по Сбер ID в формате app://host, используется к примеру при бесшовной авторизации по Сбер ID Для стандартной авторизации по кнопке не требуется.
	 * @param loginHint Для передачи телефона на OIDC страницу.
	 */
	suspend fun createUriForLogin(
		clientId: String,
		scope: String,
		state: String,
		nonce: String,
		redirectUri: String,
		customTabRedirectUri: String?,
		codeChallenge: String?,
		codeChallengeMethod: String?,
		uriScheme: String?,
		loginHint: String?,
	): Uri = runIfInitialized {
		SID.Login.createUriForLogin(
			clientId = clientId,
			scope = scope,
			state = state,
			nonce = nonce,
			redirectUri = redirectUri,
			customTabRedirectUri = customTabRedirectUri,
			codeChallenge = codeChallenge,
			codeChallengeMethod = codeChallengeMethod,
			uriScheme = uriScheme,
			loginHint = loginHint
		)
	}


	/**
	 * Запуск аутентификации через Сбер ID. Если МП Сбол не установленно, запустится web версия.
	 * Метод умеет запускать диалог выбора способа входа, пока диалога под настройкой
	 *
	 * @param activity Для получения информации о МП Сбол и старте активити.
	 * @param uri URI с данными для запуска активити.
	 */
	suspend fun loginWithID(activity: FragmentActivity, uri: Uri) = runIfInitialized {
		SID.Login.loginWithID(
			activity = activity,
			uri = uri
		)
	}




	/**
	 * Внимание Если вам нужна обычная авторизация по Сбер ID используйте loginWithSberbankID.
	 * Запускает аутентификации через Сбер ID в CustomTabs. С последующей авторизацией через web страницу Сбер ID.
	 *
	 * @param context Для получения информации о МП Сбол и старте активити.
	 * @param uri URI с данными для запуска активити.
	 */
	suspend fun loginWithIDToCustomTab(context: FragmentActivity, uri: Uri) = runIfInitialized {
		SID.Login.loginWithIDToCustomTab(
			context = context,
			uri = uri
		)
	}


	/**
	 * Разлогинить клиента.
	 */
	suspend fun logout() = runIfInitialized {
		SID.Login.logout()
	}


	/**
	 * Метод для получения результата аутентификации через Сбер ID.
	 *
	 * @param intent  содержит данные ответа от Сбер ID
	 */
	suspend fun getIDAuthResult(intent: Intent?): SberIDResultModel? = runIfInitialized {
		if (!Utils.getInitialized() || intent == null) return@runIfInitialized null
		val model = SID.Login.getIDAuthResult(intent = intent)
		model
	}


	/**
	 * Отправка события результата isSuccess входа через другой сервис nameAuthService.
	 * Например вход через google: sendOtherAuth(NameAuthService.GOOGLE, success)
	 *
	 * @param nameAuthService Название сервиса авторизации.
	 * @param isSuccess Успешно ли прошла авторизация.
	 * @param isUserNew Новый ли это юзер.
	 */
	suspend fun sendOtherAuth(
		nameAuthService: String,
		isSuccess: Boolean = true,
		isUserNew: Boolean? = null
	) = runIfInitialized {
		SID.Login.sendOtherAuth(
			nameAuthService = nameAuthService,
			isSuccess = isSuccess,
			isUserNew = isUserNew
		)
	}


	/**
	 * Получить схему диплинка в формате app://host для авторизации по Сбер ID
	 * из исходного Uri при переходе в МП партнера в сценарии бесшовной авторизаии
	 * Полученное значение необходимо передать в метод [SberIDBuilder.uriScheme] при построении диплинка на авторизацию
	 *
	 * @param uri исходный Uri при переходе в МП партнера при бесшовной авторизации
	 */
	suspend fun getSeamlessUriScheme(uri: Uri): String? = runIfInitialized {
		SID.Login.getSeamlessUriScheme(uri = uri)
	}


	/**
	 * Метод для получения индификатора пользователя партнёра и использовании его для ЕЛК.
	 *
	 * @param userID индификатор пользователя партнёра.
	 */
	suspend fun setUserID(userID: String) = runIfInitialized {
		SID.Login.setUserID(userID = userID)
	}

	// /**
	//  * Запуск аутентификации через СБОЛ. Если МП СБОЛ не установленно, то метод не сработает и вернёт false.
	//  *
	//  * @param context Для получения информации о СБОЛ и старте активити.
	//  * @param uri URI с данными для запуска СБОЛ.
	//  */
	// suspend fun autoAuth(
	// 	context: Context,
	// 	uri: Uri
	// ): Boolean = runIfInitialized {
	// 	    SID.Login.autoAuth(context = context, uri = uri)
	// }


	//AppToken

	/**
	 * Обновление AppToken
	 *
	 * @param scope openid mobile name
	 * @param clientId 40XXXXda-1532-11eb-XXXX-123XXXXX002
	 * @param state состояние
	 * @param nonce b1947d4XXXX4eb0a6XXXX679beXXXXX
	 * @param redirectUri https://qa.XXXXXX.ru/cas/esa/cb
	 * @param codeChallenge XXXXXXX_ENx-mWSU9qaO6o8u5CSz-8dYTyiXXXXXHp8
	 * @param codeChallengeMethod S256
	 *
	 */
	suspend fun updateAppToken(
		scope: String,
		clientId: String,
		state: String,
		nonce: String,
		redirectUri: String,
		codeChallenge: String?,
		codeChallengeMethod: String?
	) = runIfInitialized {
		val params = OIDCParams(
			scope = scope,
			clientId = clientId,
			state = state,
			nonce = nonce,
			redirectUri = redirectUri,
			codeChallenge = codeChallenge,
			codeChallengeMethod = codeChallengeMethod
		)
		SID.AppToken.updateAppToken(params)
	}


	/**
	 * Проверка на валидность AppToken
	 */
	suspend fun checkAppTokenForValid() = runIfInitialized {
		SID.AppToken.checkAppTokenForValid()
	}



	/**
	 * Горячая подписка на события фичи AppToken
	 *
	 * @param owner Владелец жизненного цикла.
	 * @param events Лямбда отдающая ответы.
	 */
	suspend fun subscribeToAppTokenEvents(owner: LifecycleOwner, events: (AppTokenEvents) -> Unit) = runIfInitialized {
		SID.AppToken.subscribeToEvents(owner = owner, events = events)
	}


	/**
	 * Открытие линки с авторизованной сесси партнёра
	 *
	 * @param activity Активити с жизненным циклом.
	 * @param webLink линк сервиса, в который входим
	 * @param clientId id клиента сервиса, в который входим
	 * @param partnerColorInt цвет партнёра, для окрашивания UI под партнера в которого входим
	 * @param openIn способ открытия, если нужен - webview/browser
	 */
	suspend fun goToPartnerSSO(
		activity: FragmentActivity,
		webLink: String,
		clientId: String?,
		partnerColorInt: Int?,
		openIn: String?
	) = runIfInitialized {
		val linkInformation = LinkInformation(
			webLink = webLink,
			clientId = clientId,
			partnerColorInt = partnerColorInt,
			openIn = openIn
		)
		SID.AppToken.goToPartnerSSO(activity, linkInformation)
	}


	/**
	 * Вернуть [App2SdkModel] для открытия сессии внутри другого sdk в рамках одного приложения
	 *
	 * @param clientId        Id партнерского приложения
	 * @param partnerLink     Cсылка на сервис партнера
	 */
	suspend fun getApp2SdkToken(
		clientId: String,
		partnerLink: String,
	): App2SdkModel? = runIfInitialized {
		SID.AppToken.getApp2SdkToken(clientId = clientId, partnerLink = partnerLink)
	}

	//ELK

	/**
	 * Подписка на события ЕЛК для партнёров, отдаёт события на какой элемент было осщуетсвлено нажатие и его урл.
	 *
	 * @param owner     владелец жизненного цикла.
	 * @param events    лямбда с событиями.
	 */
	suspend fun subscribeToAnalytic(owner: LifecycleOwner, events: (String) -> Unit) = runIfInitialized {
		SID.ELK.subscribeToAnalytic(owner, events)
	}


	/**
	 * Метод для установки текущего режима UI через enum [UIColorMode].
	 *
	 * @param uiColorMode enum цветового режима.
	 */
	suspend fun setUIMode(
		uiColorMode: UIColorMode
	) = runIfInitialized {
		SID.ELK.setUIMode(uiColorMode)
	}

	/**
	 * Метод для установки кастомного шрифта через класс [CustomFonts].
	 *
	 * @param mediumResID   ресурс с обычным шрифтом.
	 * @param semiBoldResID   ресурс с жирным шрифтом.
	 */
	suspend fun setCustomFonts(
		mediumResID: Int?,
		semiBoldResID: Int?
	) = runIfInitialized {
		val customFonts = CustomFonts(
			semiBoldResID = semiBoldResID,
			mediumResID = mediumResID
		)
		SID.ELK.setCustomFonts(customFonts = customFonts)
	}

	//Events
	// /**
	//  * Подписка на события ЕЛК для партнёров, отдаёт события [SIDEvents].
	//  * Из которых реализовано -
	//  * [AppTokenEvents], [ELKEvents]
	//  *
	//  * @param owner     владелец жизненного цикла.
	//  * @param events    лямбда с событиями.
	//  */
	//
	// suspend fun subscribeForEvents(owner: LifecycleOwner, events: (SIDEvents) -> Unit) = runIfInitialized {
	// 	SID.Events.subscribeForEvents(owner, events)
	// }

}